#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/reboot.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/mfd/core.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/err.h>
#include <linux/power/aw_pm.h>
#include "../axp-core.h"
#include "../axp-charger.h"
#include "axp259.h"

static struct axp_dev *axp259_pm_power;
struct axp_config_info axp259_config;
struct wakeup_source *axp259_ws;
static int axp259_pmu_num;

static struct axp_regmap_irq_chip axp259_regmap_irq_chip = {
	.name           = "axp259_irq_chip",
	.status_base    = AXP259_INTSTS1,
	.enable_base    = AXP259_INTEN1,
	.num_regs       = 5,
};

static struct resource axp259_pek_resources[] = {
	{AXP259_IRQ_PEKRE,  AXP259_IRQ_PEKRE,  "PEK_DBR",      IORESOURCE_IRQ,},
	{AXP259_IRQ_PEKFE,  AXP259_IRQ_PEKFE,  "PEK_DBF",      IORESOURCE_IRQ,},
};

static struct resource axp259_charger_resources[] = {
	{AXP259_IRQ_ACIN,   AXP259_IRQ_ACIN,   "ac in",        IORESOURCE_IRQ,},
	{AXP259_IRQ_ACRE,   AXP259_IRQ_ACRE,   "ac out",       IORESOURCE_IRQ,},
	{AXP259_IRQ_BATIN,  AXP259_IRQ_BATIN,  "bat in",       IORESOURCE_IRQ,},
	{AXP259_IRQ_BATRE,  AXP259_IRQ_BATRE,  "bat out",      IORESOURCE_IRQ,},
	{AXP259_IRQ_CHAST,  AXP259_IRQ_CHAST,  "charging",     IORESOURCE_IRQ,},
	{AXP259_IRQ_CHAOV,  AXP259_IRQ_CHAOV,  "charge over",  IORESOURCE_IRQ,},
	{AXP259_IRQ_LOWN1,  AXP259_IRQ_LOWN1,  "low warning1", IORESOURCE_IRQ,},
	{AXP259_IRQ_LOWN2,  AXP259_IRQ_LOWN2,  "low warning2", IORESOURCE_IRQ,},
};

static struct mfd_cell axp259_cells[] = {
	{
		.name          = "axp259-powerkey",
		.num_resources = ARRAY_SIZE(axp259_pek_resources),
		.resources     = axp259_pek_resources,
	},
	{
		.name          = "axp259-charger",
		.num_resources = ARRAY_SIZE(axp259_charger_resources),
		.resources     = axp259_charger_resources,
	},
};

void axp259_power_off(void)
{
	pr_info("[%s] send power-off command!\n", axp_name[axp259_pmu_num]);
	axp_regmap_set_bits(axp259_pm_power->regmap, AXP259_OFF_CTL, 0x40);
	mdelay(20);
	pr_warn("[%s] warning!!! axp can't power-off,\"\
		\" maybe some error happend!\n", axp_name[axp259_pmu_num]);
}

static int axp259_init_chip(struct axp_dev *axp259)
{
	uint8_t chip_id;
	int err;

	err = axp_regmap_write(axp259->regmap, AXP259_ADDR_EXTENSION, 0x40);
	if (err) {
		pr_err("[%s] try to write addr failed!\n",
				axp_name[axp259_pmu_num]);
		return err;
	}

	err = axp_regmap_read(axp259->regmap, AXP259_IC_TYPE, &chip_id);
	if (err) {
		pr_err("[%s] try to read chip id failed!\n",
				axp_name[axp259_pmu_num]);
		return err;
	}

	if (((chip_id & 0xc0) == 0x00) &&
		((chip_id & 0x0f) == 0x04)
		) {
		pr_info("[%s] chip id detect 0x%x !\n",
				axp_name[axp259_pmu_num], chip_id);
	} else {
		pr_info("[%s] chip id not detect 0x%x !\n",
				axp_name[axp259_pmu_num], chip_id);
	}

	/*Init 16's Reset PMU en */
	if (axp259_config.pmu_reset)
		axp_regmap_set_bits(axp259->regmap, 0x29, 0x04); /* enable */
	else
		axp_regmap_clr_bits(axp259->regmap, 0x29, 0x04); /* disable */

	/*Init IRQ wakeup en*/
	if (axp259_config.pmu_irq_wakeup)
		axp_regmap_set_bits(axp259->regmap, 0x26, 0x40); /* enable */
	else
		axp_regmap_clr_bits(axp259->regmap, 0x26, 0x40); /* disable */

	/*Init PMU Over Temperature protection*/
	if (axp259_config.pmu_hot_shutdown)
		axp_regmap_set_bits(axp259->regmap, 0xf3, 0x08); /* enable */
	else
		axp_regmap_clr_bits(axp259->regmap, 0xf3, 0x08); /* disable */

	return 0;
}

static void axp259_wakeup_event(void)
{
	__pm_wakeup_event(axp259_ws, 0);
}

static s32 axp259_usb_det(void)
{
	return 0;
}

static s32 axp259_usb_vbus_output(int high)
{
	u8 ret = 0;

	return ret;
}

static int axp259_cfg_pmux_para(int num, struct aw_pm_info *api, int *pmu_id)
{
	char name[8];
	struct device_node *np;

	sprintf(name, "pmu%d", num);

	np = of_find_node_by_type(NULL, name);
	if (NULL == np) {
		pr_err("can not find device_type for %s\n", name);
		return -1;
	}

	if (!of_device_is_available(np)) {
		pr_err("can not find node for %s\n", name);
		return -1;
	}

	api->pmu_arg.twi_port = axp259_pm_power->regmap->client->adapter->nr;
	api->pmu_arg.dev_addr = axp259_pm_power->regmap->client->addr;
	*pmu_id = axp259_config.pmu_id;

	return 0;
}

static const char *axp259_get_pmu_name(void)
{
	return axp_name[axp259_pmu_num];
}

static struct axp_dev *axp259_get_pmu_dev(void)
{
	return axp259_pm_power;
}

struct axp_platform_ops axp259_platform_ops = {
	.usb_det = axp259_usb_det,
	.usb_vbus_output = axp259_usb_vbus_output,
	.cfg_pmux_para = axp259_cfg_pmux_para,
	.get_pmu_name = axp259_get_pmu_name,
	.get_pmu_dev  = axp259_get_pmu_dev,
	.powerkey_name = {
		"axp259-powerkey",
	},
	.charger_name = {
		"axp259-charger",
	},
};

static const struct i2c_device_id axp259_id_table[] = {
	{ "axp259", 0 },
	{}
};

static const struct of_device_id axp259_dt_ids[] = {
	{ .compatible = "axp259", },
	{},
};
MODULE_DEVICE_TABLE(of, axp259_dt_ids);

static int axp259_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{
	int ret;
	struct axp_dev *axp259;
	struct device_node *node = client->dev.of_node;

	axp259_pmu_num = axp_get_pmu_num(axp259_dt_ids,
				ARRAY_SIZE(axp259_dt_ids));
	if (axp259_pmu_num < 0) {
		pr_err("%s get pmu num failed\n", __func__);
		return axp259_pmu_num;
	}

	if (node) {
		/* get dt and sysconfig */
		if (!of_device_is_available(node)) {
			axp259_config.pmu_used = 0;
			pr_err("%s: pmu_used = %u\n", __func__,
					axp259_config.pmu_used);
			return -EPERM;
		} else {
			axp259_config.pmu_used = 1;
			ret = axp_dt_parse(node, axp259_pmu_num,
					&axp259_config);
			if (ret) {
				pr_err("%s parse device tree err\n", __func__);
				return -EINVAL;
			}
		}
	} else {
		pr_err("AXP22x device tree err!\n");
		return -EBUSY;
	}

	axp259 = devm_kzalloc(&client->dev, sizeof(*axp259), GFP_KERNEL);
	if (!axp259)
		return -ENOMEM;

	axp259->dev = &client->dev;
	axp259->nr_cells = ARRAY_SIZE(axp259_cells);
	axp259->cells = axp259_cells;
	axp259->pmu_num = axp259_pmu_num;

	ret = axp_mfd_cell_name_init(&axp259_platform_ops,
				ARRAY_SIZE(axp259_dt_ids), axp259->pmu_num,
				axp259->nr_cells, axp259->cells);
	if (ret)
		return ret;

	axp259->regmap = axp_regmap_init_i2c(&client->dev);
	if (IS_ERR(axp259->regmap)) {
		ret = PTR_ERR(axp259->regmap);
		dev_err(&client->dev, "regmap init failed: %d\n", ret);
		return ret;
	}

	ret = axp259_init_chip(axp259);
	if (ret)
		return ret;

	ret = axp_mfd_add_devices(axp259);
	if (ret) {
		dev_err(axp259->dev, "failed to add MFD devices: %d\n", ret);
		return ret;
	}

	axp259->irq_data = axp_irq_chip_register(axp259->regmap, client->irq,
						IRQF_SHARED
						| IRQF_DISABLED
						| IRQF_NO_SUSPEND,
						&axp259_regmap_irq_chip,
						axp259_wakeup_event);
	if (IS_ERR(axp259->irq_data)) {
		ret = PTR_ERR(axp259->irq_data);
		dev_err(&client->dev, "axp init irq failed: %d\n", ret);
		return ret;
	}

	axp259_pm_power = axp259;

	if (!pm_power_off)
		pm_power_off = axp259_power_off;

	axp_platform_ops_set(axp259->pmu_num, &axp259_platform_ops);

	axp259_ws = wakeup_source_register("axp259_wakeup_source");

	return 0;
}

static int axp259_remove(struct i2c_client *client)
{
	struct axp_dev *axp259 = i2c_get_clientdata(client);

	if (axp259 == axp259_pm_power) {
		axp259_pm_power = NULL;
		pm_power_off = NULL;
	}

	axp_mfd_remove_devices(axp259);
	axp_irq_chip_unregister(client->irq, axp259->irq_data);

	return 0;
}

static int axp259_suspend(struct i2c_client *client, pm_message_t mesg)
{
	int ret;
	struct axp_dev *axp259 = i2c_get_clientdata(client);

	ret = axp_regmap_set_bits(axp259->regmap, 0x28, 0x20);
	if (ret) {
		pr_err("%s:%d try to write failed!\n",
				__func__, __LINE__);
		goto out;
	}

	ret = axp_regmap_set_bits(axp259->regmap, 0x26, 0x04);
	if (ret) {
		pr_err("%s:%d try to write failed!\n",
				__func__, __LINE__);
		goto out;
	}

	return 0;

out:
	return ret;
}

static struct i2c_driver axp259_driver = {
	.driver = {
		.name   = "axp259",
		.owner  = THIS_MODULE,
		.of_match_table = axp259_dt_ids,
	},
	.probe      = axp259_probe,
	.remove     = axp259_remove,
	.suspend    = axp259_suspend,
	.id_table   = axp259_id_table,
};

static int __init axp259_i2c_init(void)
{
	int ret;
	ret = i2c_add_driver(&axp259_driver);
	if (ret != 0)
		pr_err("Failed to register axp259 I2C driver: %d\n", ret);
	return ret;
}
subsys_initcall(axp259_i2c_init);

static void __exit axp259_i2c_exit(void)
{
	i2c_del_driver(&axp259_driver);
}
module_exit(axp259_i2c_exit);

MODULE_DESCRIPTION("PMIC Driver for AXP259");
MODULE_AUTHOR("Qin <qinyongshen@allwinnertech.com>");
MODULE_LICENSE("GPL");
